/* NHRP vty handling
 * Copyright (c) 2014-2015 Timo Teräs
 *
 * This file is free software: you may copy, redistribute and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 */

#include "zebra.h"
#include "command.h"
#include "zclient.h"
#include "stream.h"

#include "nhrpd.h"
#include "netlink.h"

static struct cmd_node zebra_node = {
	.node   = ZEBRA_NODE,
	.prompt = "%s(config-router)# ",
	.vtysh  = 1,
};

static struct cmd_node nhrp_interface_node = {
	.node   = INTERFACE_NODE,
	.prompt = "%s(config-if)# ",
	.vtysh  = 1,
};

#define NHRP_DEBUG_FLAGS_CMD "(all|common|event|interface|kernel|route|vici)"

#define NHRP_DEBUG_FLAGS_STR		\
	"All messages\n"		\
	"Common messages (default)\n"	\
	"Event manager messages\n"	\
	"Interface messages\n"		\
	"Kernel messages\n"		\
	"Route messages\n"		\
	"VICI messages\n"

static const struct message debug_flags_desc[] = {
	{ NHRP_DEBUG_ALL, "all" },
	{ NHRP_DEBUG_COMMON, "common" },
	{ NHRP_DEBUG_IF, "interface" },
	{ NHRP_DEBUG_KERNEL, "kernel" },
	{ NHRP_DEBUG_ROUTE, "route" },
	{ NHRP_DEBUG_VICI, "vici" },
	{ NHRP_DEBUG_EVENT, "event" },
	{ 0, NULL },
};

static const struct message interface_flags_desc[] = {
	{ NHRP_IFF_SHORTCUT, "shortcut" },
	{ NHRP_IFF_REDIRECT, "redirect" },
	{ NHRP_IFF_REG_NO_UNIQUE, "registration no-unique" },
	{ 0, NULL },
};

static int nhrp_vty_return(struct vty *vty, int ret)
{
	static const char * const errmsgs[] = {
		[NHRP_ERR_FAIL]				= "Command failed",
		[NHRP_ERR_NO_MEMORY]			= "Out of memory",
		[NHRP_ERR_UNSUPPORTED_INTERFACE]	= "NHRP not supported on this interface",
		[NHRP_ERR_NHRP_NOT_ENABLED]		= "NHRP not enabled (set 'nhrp network-id' first)",
		[NHRP_ERR_ENTRY_EXISTS]			= "Entry exists already",
		[NHRP_ERR_ENTRY_NOT_FOUND]		= "Entry not found",
		[NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH]	= "Protocol address family does not match command (ip/ipv6 mismatch)",
	};
	const char *str = NULL;
	char buf[256];

	if (ret == NHRP_OK)
		return CMD_SUCCESS;

	if (ret > 0 && ret <= (int)ZEBRA_NUM_OF(errmsgs))
		if (errmsgs[ret])
			str = errmsgs[ret];

	if (!str) {
		str = buf;
		snprintf(buf, sizeof(buf), "Unknown error %d", ret);
	}

	vty_out (vty, "%% %s%s", str, VTY_NEWLINE);

	return CMD_WARNING;
}

static int toggle_flag(
	struct vty *vty, const struct message *flag_desc,
	const char *name, int on_off, unsigned *flags)
{
	int i;

	for (i = 0; flag_desc[i].str != NULL; i++) {
		if (strcmp(flag_desc[i].str, name) != 0)
			continue;
		if (on_off)
			*flags |= flag_desc[i].key;
		else
			*flags &= ~flag_desc[i].key;
		return CMD_SUCCESS;
	}

	vty_out(vty, "%% Invalid value %s%s", name, VTY_NEWLINE);
	return CMD_WARNING;
}

#ifndef NO_DEBUG

DEFUN(show_debugging_nhrp, show_debugging_nhrp_cmd,
	"show debugging nhrp",
	SHOW_STR
	"Debugging information\n"
	"NHRP configuration\n")
{
	int i;

	vty_out(vty, "NHRP debugging status:%s", VTY_NEWLINE);

	for (i = 0; debug_flags_desc[i].str != NULL; i++) {
		if (debug_flags_desc[i].key == NHRP_DEBUG_ALL)
			continue;
		if (!(debug_flags_desc[i].key & debug_flags))
			continue;

		vty_out(vty, "  NHRP %s debugging is on%s",
			debug_flags_desc[i].str, VTY_NEWLINE);
	}

	return CMD_SUCCESS;
}

DEFUN(debug_nhrp, debug_nhrp_cmd,
	"debug nhrp " NHRP_DEBUG_FLAGS_CMD,
	"Enable debug messages for specific or all parts.\n"
	"NHRP information\n"
	NHRP_DEBUG_FLAGS_STR)
{
	return toggle_flag(vty, debug_flags_desc, argv[0], 1, &debug_flags);
}

DEFUN(no_debug_nhrp, no_debug_nhrp_cmd,
	"no debug nhrp " NHRP_DEBUG_FLAGS_CMD,
	NO_STR
	"Disable debug messages for specific or all parts.\n"
	"NHRP information\n"
	NHRP_DEBUG_FLAGS_STR)
{
	return toggle_flag(vty, debug_flags_desc, argv[0], 0, &debug_flags);
}

#endif /* NO_DEBUG */

static int nhrp_config_write(struct vty *vty)
{
#ifndef NO_DEBUG
	if (debug_flags == NHRP_DEBUG_ALL) {
		vty_out(vty, "debug nhrp all%s", VTY_NEWLINE);
	} else {
		int i;

		for (i = 0; debug_flags_desc[i].str != NULL; i++) {
			if (debug_flags_desc[i].key == NHRP_DEBUG_ALL)
				continue;
			if (!(debug_flags & debug_flags_desc[i].key))
				continue;
			vty_out(vty, "debug nhrp %s%s", debug_flags_desc[i].str, VTY_NEWLINE);
		}
	}
	vty_out(vty, "!%s", VTY_NEWLINE);
#endif /* NO_DEBUG */

	if (nhrp_event_socket_path) {
		vty_out(vty, "nhrp event socket %s%s",
			nhrp_event_socket_path, VTY_NEWLINE);
	}
	if (netlink_nflog_group) {
		vty_out(vty, "nhrp nflog-group %d%s",
			netlink_nflog_group, VTY_NEWLINE);
	}

	return 0;
}

#define IP_STR		"IP information\n"
#define IPV6_STR	"IPv6 information\n"
#define AFI_CMD		"(ip|ipv6)"
#define AFI_STR		IP_STR IPV6_STR
#define NHRP_STR	"Next Hop Resolution Protocol functions\n"

static afi_t cmd_to_afi(const char *cmd)
{
	return strncmp(cmd, "ipv6", 4) == 0 ? AFI_IP6 : AFI_IP;
}

static const char *afi_to_cmd(afi_t afi)
{
	if (afi == AFI_IP6) return "ipv6";
	return "ip";
}

DEFUN(nhrp_event_socket, nhrp_event_socket_cmd,
	"nhrp event socket SOCKET",
	NHRP_STR
	"Event Manager commands\n"
	"Event Manager unix socket path\n"
	"Unix path for the socket")
{
	evmgr_set_socket(argv[0]);
	return CMD_SUCCESS;
}

DEFUN(no_nhrp_event_socket, no_nhrp_event_socket_cmd,
	"no nhrp event socket [SOCKET]",
	NO_STR
	NHRP_STR
	"Event Manager commands\n"
	"Event Manager unix socket path\n"
	"Unix path for the socket")
{
	evmgr_set_socket(NULL);
	return CMD_SUCCESS;
}

DEFUN(nhrp_nflog_group, nhrp_nflog_group_cmd,
	"nhrp nflog-group <1-65535>",
	NHRP_STR
	"Specify NFLOG group number\n"
	"NFLOG group number\n")
{
	uint32_t nfgroup;

	VTY_GET_INTEGER_RANGE("nflog-group", nfgroup, argv[0], 1, 65535);
	netlink_set_nflog_group(nfgroup);

	return CMD_SUCCESS;
}

DEFUN(no_nhrp_nflog_group, no_nhrp_nflog_group_cmd,
	"no nhrp nflog-group [<1-65535>]",
	NO_STR
	NHRP_STR
	"Specify NFLOG group number\n"
	"NFLOG group number\n")
{
	netlink_set_nflog_group(0);
	return CMD_SUCCESS;
}

DEFUN(tunnel_protection, tunnel_protection_cmd,
	"tunnel protection vici profile PROFILE {fallback-profile FALLBACK}",
	"NHRP/GRE integration\n"
	"IPsec protection\n"
	"VICI (StrongSwan)\n"
	"IPsec profile\n"
	"IPsec profile name\n"
	"Fallback IPsec profile\n"
	"Fallback IPsec profile name\n")
{
	struct interface *ifp = vty->index;

	nhrp_interface_set_protection(ifp, argv[0], argv[1]);
	return CMD_SUCCESS;
}

DEFUN(no_tunnel_protection, no_tunnel_protection_cmd,
	"no tunnel protection",
	NO_STR
	"NHRP/GRE integration\n"
	"IPsec protection\n")
{
	struct interface *ifp = vty->index;

	nhrp_interface_set_protection(ifp, NULL, NULL);
	return CMD_SUCCESS;
}

DEFUN(tunnel_source, tunnel_source_cmd,
	"tunnel source INTERFACE",
	"NHRP/GRE integration\n"
	"Tunnel device binding tracking\n"
	"Interface name\n")
{
	struct interface *ifp = vty->index;
	nhrp_interface_set_source(ifp, argv[0]);
	return CMD_SUCCESS;
}

DEFUN(no_tunnel_source, no_tunnel_source_cmd,
	"no tunnel source",
	"NHRP/GRE integration\n"
	"Tunnel device binding tracking\n"
	"Interface name\n")
{
	struct interface *ifp = vty->index;
	nhrp_interface_set_source(ifp, NULL);
	return CMD_SUCCESS;
}

DEFUN(if_nhrp_network_id, if_nhrp_network_id_cmd,
	AFI_CMD " nhrp network-id <1-4294967295>",
	AFI_STR
	NHRP_STR
	"Enable NHRP and specify network-id\n"
	"System local ID to specify interface group\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	VTY_GET_INTEGER_RANGE("network-id", nifp->afi[afi].network_id, argv[1], 1, 4294967295);
	nhrp_interface_update(ifp);

	return CMD_SUCCESS;
}

DEFUN(if_no_nhrp_network_id, if_no_nhrp_network_id_cmd,
	"no " AFI_CMD " nhrp network-id [<1-4294967295>]",
	NO_STR
	AFI_STR
	NHRP_STR
	"Enable NHRP and specify network-id\n"
	"System local ID to specify interface group\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	nifp->afi[afi].network_id = 0;
	nhrp_interface_update(ifp);

	return CMD_SUCCESS;
}

DEFUN(if_nhrp_flags, if_nhrp_flags_cmd,
	AFI_CMD " nhrp (shortcut|redirect)",
	AFI_STR
	NHRP_STR
	"Allow shortcut establishment\n"
	"Send redirect notifications\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	return toggle_flag(vty, interface_flags_desc, argv[1], 1, &nifp->afi[afi].flags);
}

DEFUN(if_no_nhrp_flags, if_no_nhrp_flags_cmd,
	"no " AFI_CMD " nhrp (shortcut|redirect)",
	NO_STR
	AFI_STR
	NHRP_STR
	"Allow shortcut establishment\n"
	"Send redirect notifications\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	return toggle_flag(vty, interface_flags_desc, argv[1], 0, &nifp->afi[afi].flags);
}

DEFUN(if_nhrp_reg_flags, if_nhrp_reg_flags_cmd,
	AFI_CMD " nhrp registration (no-unique)",
	AFI_STR
	NHRP_STR
	"Registration configuration\n"
	"Don't set unique flag\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);
	char name[256];
	snprintf(name, sizeof(name), "registration %s", argv[1]);
	return toggle_flag(vty, interface_flags_desc, name, 1, &nifp->afi[afi].flags);
}

DEFUN(if_no_nhrp_reg_flags, if_no_nhrp_reg_flags_cmd,
	"no " AFI_CMD " nhrp registration (no-unique)",
	NO_STR
	AFI_STR
	NHRP_STR
	"Registration configuration\n"
	"Don't set unique flag\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);
	char name[256];
	snprintf(name, sizeof(name), "registration %s", argv[1]);
	return toggle_flag(vty, interface_flags_desc, name, 0, &nifp->afi[afi].flags);
}

DEFUN(if_nhrp_holdtime, if_nhrp_holdtime_cmd,
	AFI_CMD " nhrp holdtime <1-65000>",
	AFI_STR
	NHRP_STR
	"Specify NBMA address validity time\n"
	"Time in seconds that NBMA addresses are advertised valid\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	VTY_GET_INTEGER_RANGE("holdtime", nifp->afi[afi].holdtime, argv[1], 1, 65000);
	nhrp_interface_update(ifp);

	return CMD_SUCCESS;
}

DEFUN(if_no_nhrp_holdtime, if_no_nhrp_holdtime_cmd,
	"no " AFI_CMD " nhrp holdtime [1-65000]",
	NO_STR
	AFI_STR
	NHRP_STR
	"Specify NBMA address validity time\n"
	"Time in seconds that NBMA addresses are advertised valid\n")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;
	afi_t afi = cmd_to_afi(argv[0]);

	nifp->afi[afi].holdtime = NHRPD_DEFAULT_HOLDTIME;
	nhrp_interface_update(ifp);

	return CMD_SUCCESS;
}

DEFUN(if_nhrp_mtu, if_nhrp_mtu_cmd,
	"ip nhrp mtu (<576-1500>|opennhrp)",
	IP_STR
	NHRP_STR
	"Configure NHRP advertised MTU\n"
	"MTU value\n"
	"Advertise bound interface MTU similar to OpenNHRP")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;

	if (argv[0][0] == 'o') {
		nifp->afi[AFI_IP].configured_mtu = -1;
	} else {
		VTY_GET_INTEGER_RANGE("mtu", nifp->afi[AFI_IP].configured_mtu, argv[0], 576, 1500);
	}
	nhrp_interface_update_mtu(ifp, AFI_IP);

	return CMD_SUCCESS;
}

DEFUN(if_no_nhrp_mtu, if_no_nhrp_mtu_cmd,
	"no ip nhrp mtu [(<576-1500>|opennhrp)]",
	NO_STR
	IP_STR
	NHRP_STR
	"Configure NHRP advertised MTU\n"
	"MTU value\n"
	"Advertise bound interface MTU similar to OpenNHRP")
{
	struct interface *ifp = vty->index;
	struct nhrp_interface *nifp = ifp->info;

	nifp->afi[AFI_IP].configured_mtu = 0;
	nhrp_interface_update_mtu(ifp, AFI_IP);
	return CMD_SUCCESS;
}

DEFUN(if_nhrp_map, if_nhrp_map_cmd,
	AFI_CMD " nhrp map (A.B.C.D|X:X::X:X) (A.B.C.D|local)",
	AFI_STR
	NHRP_STR
	"Nexthop Server configuration\n"
	"IPv4 protocol address\n"
	"IPv6 protocol address\n"
	"IPv4 NBMA address\n"
	"Handle protocol address locally\n")
{
	struct interface *ifp = vty->index;
	afi_t afi = cmd_to_afi(argv[0]);
	union sockunion proto_addr, nbma_addr;
	struct nhrp_cache *c;

	if (str2sockunion(argv[1], &proto_addr) < 0 ||
	    afi2family(afi) != sockunion_family(&proto_addr))
		return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH);

	c = nhrp_cache_get(ifp, &proto_addr, 1);
	if (!c)
		return nhrp_vty_return(vty, NHRP_ERR_FAIL);

	c->map = 1;
	if (strcmp(argv[2], "local") == 0) {
		nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
	} else{
		if (str2sockunion(argv[2], &nbma_addr) < 0)
			return nhrp_vty_return(vty, NHRP_ERR_FAIL);
		nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
			nhrp_peer_get(ifp, &nbma_addr), 0, NULL);
	}

	return CMD_SUCCESS;
}

DEFUN(if_nhrp_nhs, if_nhrp_nhs_cmd,
	AFI_CMD " nhrp nhs (A.B.C.D|X:X::X:X|dynamic) nbma (A.B.C.D|FQDN)",
	AFI_STR
	NHRP_STR
	"Nexthop Server configuration\n"
	"IPv4 protocol address\n"
	"IPv6 protocol address\n"
	"Automatic detection of protocol address\n"
	"IPv4 NBMA address\n"
	"Fully qualified domain name for NBMA address(es)\n")
{
	struct interface *ifp = vty->index;
	afi_t afi = cmd_to_afi(argv[0]);
	union sockunion proto_addr;
	int ret;

	if (str2sockunion(argv[1], &proto_addr) < 0)
		sockunion_family(&proto_addr) = AF_UNSPEC;

	ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[2]);
	return nhrp_vty_return(vty, ret);
}

DEFUN(if_no_nhrp_nhs, if_no_nhrp_nhs_cmd,
	"no " AFI_CMD " nhrp nhs (A.B.C.D|X:X::X:X|dynamic) nbma (A.B.C.D|FQDN)",
	NO_STR
	AFI_STR
	NHRP_STR
	"Nexthop Server configuration\n"
	"IPv4 protocol address\n"
	"IPv6 protocol address\n"
	"Automatic detection of protocol address\n"
	"IPv4 NBMA address\n"
	"Fully qualified domain name for NBMA address(es)\n")
{
	struct interface *ifp = vty->index;
	afi_t afi = cmd_to_afi(argv[0]);
	union sockunion proto_addr;
	int ret;

	if (str2sockunion(argv[1], &proto_addr) < 0)
		sockunion_family(&proto_addr) = AF_UNSPEC;

	ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[2]);
	return nhrp_vty_return(vty, ret);
}

struct info_ctx {
	struct vty *vty;
	afi_t afi;
	int count;
};

static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx)
{
	struct info_ctx *ctx = pctx;
	struct vty *vty = ctx->vty;
	char buf[2][SU_ADDRSTRLEN];

	if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
		return;

	if (!ctx->count) {
		vty_out(vty, "%-8s %-8s %-24s %-24s %-6s %s%s",
			"Iface",
			"Type",
			"Protocol",
			"NBMA",
			"Flags",
			"Identity",
			VTY_NEWLINE);
	}
	ctx->count++;

	vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %c%c%c    %s%s",
		c->ifp->name,
		nhrp_cache_type_str[c->cur.type],
		sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
		c->cur.peer ? sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]) : "-",
		c->used ? 'U' : ' ',
		c->t_timeout ? 'T' : ' ',
		c->t_auth ? 'A' : ' ',
		c->cur.peer ? c->cur.peer->vc->remote.id : "-",
		VTY_NEWLINE);
}

static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx)
{
	struct info_ctx *ctx = pctx;
	struct vty *vty = ctx->vty;
	char buf[SU_ADDRSTRLEN];

	if (ctx->afi != family2afi(sockunion_family(&c->remote_addr)))
		return;

	vty_out(ctx->vty,
		"Type: %s%s"
		"Flags:%s%s%s"
		"Protocol-Address: %s/%zu%s",
		nhrp_cache_type_str[c->cur.type],
		VTY_NEWLINE,
		(c->cur.peer && c->cur.peer->online) ? " up": "",
		c->used ? " used": "",
		VTY_NEWLINE,
		sockunion2str(&c->remote_addr, buf, sizeof buf),
		8 * family2addrsize(sockunion_family(&c->remote_addr)),
		VTY_NEWLINE);

	if (c->cur.peer) {
		vty_out(ctx->vty,
			"NBMA-Address: %s%s",
			sockunion2str(&c->cur.peer->vc->remote.nbma, buf, sizeof buf),
			VTY_NEWLINE);
	}

	if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
		vty_out(ctx->vty,
			"NBMA-NAT-OA-Address: %s%s",
			sockunion2str(&c->cur.remote_nbma_natoa, buf, sizeof buf),
			VTY_NEWLINE);
	}

	vty_out(ctx->vty, "%s", VTY_NEWLINE);
}

static void show_ip_nhrp_shortcut(struct nhrp_shortcut *s, void *pctx)
{
	struct info_ctx *ctx = pctx;
	struct nhrp_cache *c;
	struct vty *vty = ctx->vty;
	char buf1[PREFIX_STRLEN], buf2[SU_ADDRSTRLEN];

	if (!ctx->count) {
		vty_out(vty, "%-8s %-24s %-24s %s%s",
			"Type",
			"Prefix",
			"Via",
			"Identity",
			VTY_NEWLINE);
	}
	ctx->count++;

	c = s->cache;
	vty_out(ctx->vty, "%-8s %-24s %-24s %s%s",
		nhrp_cache_type_str[s->type],
		prefix2str(s->p, buf1, sizeof buf1),
		c ? sockunion2str(&c->remote_addr, buf2, sizeof buf2) : "",
		(c && c->cur.peer) ? c->cur.peer->vc->remote.id : "",
		VTY_NEWLINE);
}

DEFUN(show_ip_nhrp, show_ip_nhrp_cmd,
	"show " AFI_CMD " nhrp (cache|shortcut|opennhrp|)",
	SHOW_STR
	AFI_STR
	"NHRP information\n"
	"Forwarding cache information\n"
	"Shortcut information\n"
	"opennhrpctl style cache dump\n")
{
	struct listnode *node;
	struct interface *ifp;
	struct info_ctx ctx = {
		.vty = vty,
		.afi = cmd_to_afi(argv[0]),
	};

	if (!argv[1] || argv[1][0] == 'c') {
		for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
			nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx);
	} else if (argv[1][0] == 'o') {
		vty_out(vty, "Status: ok%s%s", VTY_NEWLINE, VTY_NEWLINE);
		ctx.count++;
		for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
			nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx);
	} else {
		nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx);
	}

	if (!ctx.count) {
		vty_out(vty, "%% No entries%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	return CMD_SUCCESS;
}

static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx)
{
	struct vty *vty = ctx;
	char buf[2][SU_ADDRSTRLEN];

	vty_out(vty, "%-24s %-24s %c      %-4d %-24s%s",
		sockunion2str(&vc->local.nbma, buf[0], sizeof buf[0]),
		sockunion2str(&vc->remote.nbma, buf[1], sizeof buf[1]),
		notifier_active(&vc->notifier_list) ? 'n' : ' ',
		vc->ipsec,
		vc->remote.id,
		VTY_NEWLINE);
}

DEFUN(show_dmvpn, show_dmvpn_cmd,
	"show dmvpn",
	SHOW_STR
	"DMVPN information\n")
{
	vty_out(vty, "%-24s %-24s %-6s %-4s %-24s%s",
		"Src",
		"Dst",
		"Flags",
		"SAs",
		"Identity",
		VTY_NEWLINE);

	nhrp_vc_foreach(show_dmvpn_entry, vty);

	return CMD_SUCCESS;
}

static void clear_nhrp_cache(struct nhrp_cache *c, void *data)
{
	struct info_ctx *ctx = data;
	if (c->cur.type <= NHRP_CACHE_CACHED) {
		nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL);
		ctx->count++;
	}
}

static void clear_nhrp_shortcut(struct nhrp_shortcut *s, void *data)
{
	struct info_ctx *ctx = data;
	nhrp_shortcut_purge(s, 1);
	ctx->count++;
}

DEFUN(clear_nhrp, clear_nhrp_cmd,
	"clear " AFI_CMD " nhrp (cache|shortcut)",
	CLEAR_STR
	AFI_STR
	NHRP_STR
	"Dynamic cache entries\n"
	"Shortcut entries\n")
{
	struct listnode *node;
	struct interface *ifp;
	struct info_ctx ctx = {
		.vty = vty,
		.afi = cmd_to_afi(argv[0]),
		.count = 0,
	};

	if (!argv[1] || argv[1][0] == 'c') {
		for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp))
			nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx);
	} else {
		nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx);
	}

	if (!ctx.count) {
		vty_out(vty, "%% No entries%s", VTY_NEWLINE);
		return CMD_WARNING;
	}

	vty_out(vty, "%% %d entries cleared%s", ctx.count, VTY_NEWLINE);
	return CMD_SUCCESS;
}

struct write_map_ctx {
	struct vty *vty;
	int family;
	const char *aficmd;
};

static void interface_config_write_nhrp_map(struct nhrp_cache *c, void *data)
{
	struct write_map_ctx *ctx = data;
	struct vty *vty = ctx->vty;
	char buf[2][SU_ADDRSTRLEN];

	if (!c->map) return;
	if (sockunion_family(&c->remote_addr) != ctx->family) return;

	vty_out(vty, " %s nhrp map %s %s%s",
		ctx->aficmd,
		sockunion2str(&c->remote_addr, buf[0], sizeof buf[0]),
		c->cur.type == NHRP_CACHE_LOCAL ? "local" :
		sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], sizeof buf[1]),
		VTY_NEWLINE);
}

static int interface_config_write(struct vty *vty)
{
	struct write_map_ctx mapctx;
	struct listnode *node;
	struct interface *ifp;
	struct nhrp_interface *nifp;
	struct nhrp_nhs *nhs;
	const char *aficmd;
	afi_t afi;
	char buf[SU_ADDRSTRLEN];
	int i;

	for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
		vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
		if (ifp->desc)
			vty_out(vty, " description %s%s", ifp->desc, VTY_NEWLINE);

		nifp = ifp->info;
		if (nifp->ipsec_profile) {
			vty_out(vty, " tunnel protection vici profile %s",
				nifp->ipsec_profile);
			if (nifp->ipsec_fallback_profile)
				vty_out(vty, " fallback-profile %s",
					nifp->ipsec_fallback_profile);
			vty_out(vty, "%s", VTY_NEWLINE);
		}
		if (nifp->source)
			vty_out(vty, " tunnel source %s%s",
				nifp->source, VTY_NEWLINE);

		for (afi = 0; afi < AFI_MAX; afi++) {
			struct nhrp_afi_data *ad = &nifp->afi[afi];

			aficmd = afi_to_cmd(afi);

			if (ad->network_id)
				vty_out(vty, " %s nhrp network-id %u%s",
					aficmd, ad->network_id,
					VTY_NEWLINE);

			if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME)
				vty_out(vty, " %s nhrp holdtime %u%s",
					aficmd, ad->holdtime,
					VTY_NEWLINE);

			if (ad->configured_mtu < 0)
				vty_out(vty, " %s nhrp mtu opennhrp%s",
					aficmd, VTY_NEWLINE);
			else if (ad->configured_mtu)
				vty_out(vty, " %s nhrp mtu %u%s",
					aficmd, ad->configured_mtu,
					VTY_NEWLINE);

			for (i = 0; interface_flags_desc[i].str != NULL; i++) {
				if (!(ad->flags & interface_flags_desc[i].key))
					continue;
				vty_out(vty, " %s nhrp %s%s",
					aficmd, interface_flags_desc[i].str, VTY_NEWLINE);
			}

			mapctx = (struct write_map_ctx) {
				.vty = vty,
				.family = afi2family(afi),
				.aficmd = aficmd,
			};
			nhrp_cache_foreach(ifp, interface_config_write_nhrp_map, &mapctx);

			list_for_each_entry(nhs, &ad->nhslist_head, nhslist_entry) {
				vty_out(vty, " %s nhrp nhs %s nbma %s%s",
					aficmd,
					sockunion_family(&nhs->proto_addr) == AF_UNSPEC ? "dynamic" : sockunion2str(&nhs->proto_addr, buf, sizeof buf),
					nhs->nbma_fqdn,
					VTY_NEWLINE);
			}
		}

		vty_out (vty, "!%s", VTY_NEWLINE);
	}

	return 0;
}

void nhrp_config_init(void)
{
	install_node(&zebra_node, nhrp_config_write);
	install_default(ZEBRA_NODE);

	/* global commands */
	install_element(VIEW_NODE, &show_debugging_nhrp_cmd);
	install_element(VIEW_NODE, &show_ip_nhrp_cmd);
	install_element(VIEW_NODE, &show_dmvpn_cmd);
	install_element(ENABLE_NODE, &show_debugging_nhrp_cmd);
	install_element(ENABLE_NODE, &show_ip_nhrp_cmd);
	install_element(ENABLE_NODE, &show_dmvpn_cmd);
	install_element(ENABLE_NODE, &clear_nhrp_cmd);

	install_element(ENABLE_NODE, &debug_nhrp_cmd);
	install_element(ENABLE_NODE, &no_debug_nhrp_cmd);

	install_element(CONFIG_NODE, &debug_nhrp_cmd);
	install_element(CONFIG_NODE, &no_debug_nhrp_cmd);

	install_element(CONFIG_NODE, &nhrp_event_socket_cmd);
	install_element(CONFIG_NODE, &no_nhrp_event_socket_cmd);
	install_element(CONFIG_NODE, &nhrp_nflog_group_cmd);
	install_element(CONFIG_NODE, &no_nhrp_nflog_group_cmd);

	/* interface specific commands */
	install_node(&nhrp_interface_node, interface_config_write);
	install_default(INTERFACE_NODE);

	install_element(CONFIG_NODE, &interface_cmd);
	install_element(CONFIG_NODE, &no_interface_cmd);
	install_element(INTERFACE_NODE, &interface_cmd);
	install_element(INTERFACE_NODE, &no_interface_cmd);
	install_element(INTERFACE_NODE, &tunnel_protection_cmd);
	install_element(INTERFACE_NODE, &no_tunnel_protection_cmd);
	install_element(INTERFACE_NODE, &tunnel_source_cmd);
	install_element(INTERFACE_NODE, &no_tunnel_source_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_network_id_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_network_id_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_holdtime_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_holdtime_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_mtu_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_mtu_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_flags_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_flags_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_reg_flags_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_reg_flags_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_map_cmd);
	install_element(INTERFACE_NODE, &if_nhrp_nhs_cmd);
	install_element(INTERFACE_NODE, &if_no_nhrp_nhs_cmd);
}
